﻿using System;
using DimensionsHelper.Common.Utils;

namespace DimensionsHelper.Common.Metadata.Script;

/// <summary>
///     元数据表达式的词法分析器
/// </summary>
public class Lexer
{
    private NumberFlag _numberFlag = NumberFlag.None;
    private string _text = string.Empty;

    /// <summary>
    ///     获取当前Token类型。
    /// </summary>
    public TokenKinds Token { get; private set; } = TokenKinds.Eof;

    /// <summary>
    ///     当前Token的起始位置。
    /// </summary>
    public int TokenStart { get; private set; }

    /// <summary>
    ///     上一个Token的结束位置。
    /// </summary>
    public int LastTokenEnd { get; private set; }


    /// <summary>
    ///     当前的文本指针位置。
    /// </summary>
    public int Position { get; private set; }


    /// <summary>
    ///     获取当前Token的文本字符串。
    /// </summary>
    public string TokenText => _text.Substring(TokenStart, Position - TokenStart);


    /// <summary>
    ///     当前Token是否是10进制数字。
    /// </summary>
    public bool IsDecimalNumber => _numberFlag == NumberFlag.Decimal;


    /// <summary>
    ///     当前Token是否是8进制数字。
    /// </summary>
    public bool IsOctalNumber => _numberFlag == NumberFlag.Octal;


    /// <summary>
    ///     当前Token是否是16进制数字。
    /// </summary>
    public bool IsHexNumber => _numberFlag == NumberFlag.Hex;


    /// <summary>
    ///     当前Token是否是小数。
    /// </summary>
    public bool IsDoubleNumber => _numberFlag == NumberFlag.Double;

    /// <summary>
    ///     设置文本内容，并重置文本指针位置和Token类型。
    /// </summary>
    /// <param name="content">待扫描的文本</param>
    public void SetText(string content)
    {
        _text = content;
        Position = 0;
        TokenStart = 0;
        LastTokenEnd = 0;
        Token = TokenKinds.Eof;
    }

    /// <summary>
    ///     获取指定位置区间的文本。
    /// </summary>
    /// <param name="start">截取字符串的起始位置</param>
    /// <param name="end">结束位置，忽略此参数时使用当前位置</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public string GetText(int start, int? end = null)
    {
        int toPos;

        if (end == null)
        {
            toPos = Position;
        }
        else
        {
            toPos = (int)end;
        }

        if (Position <= start)
        {
            throw new Exception($"无效的起始位置：{start}。");
        }

        return _text.Substring(start, toPos - start);
    }


    private char CurrentChar()
    {
        if (Position >= 0 && Position < _text.Length)
        {
            return _text[Position];
        }

        return '\0';
    }


    private char NextChar()
    {
        if (Position + 1 < _text.Length)
        {
            return _text[++Position];
        }

        // 扫描到末尾时，设置位置指针成字符串长度。
        if (Position < _text.Length)
        {
            Position = _text.Length;
        }

        return '\0';
    }


    private char LookAheadChar()
    {
        if (Position + 1 < _text.Length)
        {
            return _text[Position + 1];
        }

        return '\0';
    }


    private void SkipEmptySpace()
    {
        if (CurrentChar() != ' ')
        {
            return;
        }

        while (NextChar() == ' ')
        {
        }
    }


    private void LexDecimalNumber()
    {
        while (char.IsNumber(NextChar()))
        {
        }

        if (CurrentChar() == '.' && char.IsNumber(LookAheadChar()))
        {
            LexDouble();
            Token = TokenKinds.Double;
            _numberFlag = NumberFlag.Double;
        }
        else
        {
            Token = TokenKinds.Integer;
            _numberFlag = NumberFlag.Decimal;
        }
    }


    private void LexDouble()
    {
        while (char.IsNumber(NextChar()))
        {
        }
    }


    private void LexHexNumber()
    {
        // 跳过 &H / &h
        Position += 2;
        TokenStart = Position;

        char c = CurrentChar();

        while (char.IsNumber(c)
               || (c >= 'a' && c <= 'f')
               || (c >= 'A' && c <= 'F'))
        {
            c = NextChar();
        }

        if (Position == TokenStart)
        {
            throw new Exception($"无效的16进制数字，位置：{TokenStart + 1}");
        }

        Token = TokenKinds.Integer;
        _numberFlag = NumberFlag.Hex;
    }


    private void LexOctalNumber()
    {
        // 跳过 &O / &o
        Position += 2;
        TokenStart = Position;

        char c = CurrentChar();

        while (c >= '0' && c <= '7')
        {
            c = NextChar();
        }

        if (Position == TokenStart)
        {
            throw new Exception($"无效的8进制数字，位置：{TokenStart + 1}");
        }

        Token = TokenKinds.Integer;
        _numberFlag = NumberFlag.Octal;
    }


    private void LexToken(TokenKinds kinds, int len = 1)
    {
        Token = kinds;
        Position += len;
    }


    private void LexString()
    {
        char quotation = CurrentChar();
        char c = NextChar();

        while (c != '\0' && c != quotation)
        {
            c = NextChar();
        }

        if (c == '\0')
        {
            throw new Exception($"未终结的字符串，位置：{TokenStart}");
        }

        ++Position;
        Token = TokenKinds.String;
    }


    private void GetIdentifierToken()
    {
        switch (TokenText.ToLower())
        {
            case "and":
                Token = TokenKinds.And;
                break;
            case "or":
                Token = TokenKinds.Or;
                break;
            case "xor":
                Token = TokenKinds.Xor;
                break;
            case "mod":
                Token = TokenKinds.Mod;
                break;
            case "null":
                Token = TokenKinds.Null;
                break;
            case "true":
                Token = TokenKinds.True;
                break;
            case "false":
                Token = TokenKinds.False;
                break;
            case "not":
                Token = TokenKinds.Not;
                break;
            default:
                Token = TokenKinds.Identifier;
                break;
        }
    }


    private static void UnknownToken(int position)
    {
        throw new Exception($"未知的Token，位置：{position}。");
    }


    /// <summary>
    ///     扫描下一个Token。
    /// </summary>
    /// <returns></returns>
    public TokenKinds Lex()
    {
        // 跳过空白之前的位置是上一个Token的结束位置。
        LastTokenEnd = Position;

        if (Position >= _text.Length)
        {
            return TokenKinds.Eof;
        }

        SkipEmptySpace();

        TokenStart = Position;
        _numberFlag = NumberFlag.None;

        char c = CurrentChar();
        char ahead;

        switch (c)
        {
            case '.':
                LexToken(TokenKinds.Dot);
                break;

            case ',':
                LexToken(TokenKinds.Comma);
                break;

            case '^':
                LexToken(TokenKinds.Caret);
                break;

            case '[':
                LexToken(TokenKinds.LeftBracket);
                break;

            case ']':
                LexToken(TokenKinds.RightBracket);
                break;

            case '(':
                LexToken(TokenKinds.LeftParen);
                break;

            case ')':
                LexToken(TokenKinds.RightParen);
                break;

            case '{':
                LexToken(TokenKinds.LeftCurly);
                break;

            case '}':
                LexToken(TokenKinds.RightCurly);
                break;

            case '+':
                LexToken(TokenKinds.Add);
                break;

            case '-':
                LexToken(TokenKinds.Subtract);
                break;

            case '*':
                LexToken(TokenKinds.Star);
                break;

            case '/':
                LexToken(TokenKinds.Slash);
                break;

            case '=':
                ahead = LookAheadChar();

                if (ahead == '*')
                {
                    LexToken(TokenKinds.EqualStar, 2);
                    break;
                }

                LexToken(TokenKinds.Equal);
                break;

            case '<':
                ahead = LookAheadChar();

                if (ahead == '=')
                {
                    LexToken(TokenKinds.LessEqual, 2);
                }
                else if (ahead == '>')
                {
                    LexToken(TokenKinds.NotEqual, 2);
                }
                else
                {
                    LexToken(TokenKinds.Less);
                }

                break;

            case '>':
                ahead = LookAheadChar();

                if (ahead == '=')
                {
                    LexToken(TokenKinds.GreaterEqual, 2);
                }
                else
                {
                    LexToken(TokenKinds.Greater);
                }

                break;

            case '\'':
            case '"':
                LexString();
                break;

            case '&':
                ahead = LookAheadChar();

                if (ahead == 'h' || ahead == 'H')
                {
                    LexHexNumber();
                    break;
                }

                if (ahead == 'o' || ahead == 'O')
                {
                    LexOctalNumber();
                    break;
                }

                UnknownToken(TokenStart);
                break;

            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                LexDecimalNumber();
                break;

            default:
                // Identifier
                c = CurrentChar();

                if (StringHelper.IsValidIdentifierStartChar(c))
                {
                    while (StringHelper.IsValidIdentifierPartChar(NextChar()))
                    {
                    }

                    GetIdentifierToken();
                }
                else
                {
                    UnknownToken(TokenStart);
                }

                break;
        }

        return Token;
    }

    private enum NumberFlag
    {
        None,
        Decimal,
        Octal,
        Hex,
        Double
    }
}